KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。
KVC不需要调用明确的Get和Set方法。这样就可以在运行时动态在访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。
无论是Swift还是Objective-C,KVC的定义都是对NSObject的扩展来实现的,Objective-c中有个显式的NSKeyValueCoding
类别名,而Swift没有,也不需要。
对于所有继承了NSObject在类型,都能使用KVC。一些纯Swift类和结构体是不支持KVC的。
下面是KVC最为重要的四个方法:
|
|
当然NSKeyValueCoding类别中还有其他的一些方法,下面列举一些:
|
|
上面的这些方法在碰到特殊情况或者有特殊需求还是会用到的,所以也是可以了解一下。后面的代码示例会有讲到其中的一些方法。
同时苹果对一些容器类比如NSArray或者NSSet等,KVC有着特殊的实现。建议有基础的或者英文好的开发者直接去看苹果的官方文档,相信你会对KVC的理解更上一个台阶。
KVC的实现原理
KVC是怎么使用的,我相信绝大多数的开发者都很清楚,我在这里就不再写简单的使用KVC来设值和取值的代码了,首先我们来探讨KVC在内部是按什么样的顺序来寻找key的。
存值 setValue
当调用setValue:属性值 forKey:@”name“
的代码时,底层的执行机制如下:
程序优先调用
set:属性值
方法,代码通过setter方法完成设置。注意,这里的是指成员变量名,首字母大写要符合KVC的全名规则,下同。如果没有找到
set<Key>:
方法,KVC机制会检查:+ (BOOL)accessInstanceVariablesDirectly
方法有没有返回YES,默认该方法会返回YES;如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行:
setValue: forUNdefinedKey :
不过一般开发者不会这么做。
KVC机制会搜索该类里面有没有名为
_<Key>
的成员变量,无论该变量是在类接口部分定义,还是在类实现部分定义,也无论用了什么样的访问修饰符,只要存在以_<Key>
命名的变量,KVC都可以对该成员变量赋值。
如果该类即没有
set<Key>:
方法,也没有_<Key>
成员变量,KVC机制会搜索_is<Key>
的成员变量;
和上面一样,如果该类即没有
set:<Key>
方法,也没有_<Key>
和_is<Key>
成员变量,KVC机制再会继续搜索<Key>
和is<Key>
的成员变量。再给它们赋值;如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的:
setValue:forUNdefinedKey:
这个方法默认是抛出异常,当然你也可以对它进行重写;
如果开发者想让这个类禁用KVC里,那么重写:
+ (BOOL)accessInstanceVariablesDirectly
方法让其返回NO即可,这样的话如果KVC没有找到
set<Key>:
属性名时,会直接用:setValue:forUNdefinedKey:
Swift中没有发现accessInstanceVariablesDirectly中顺序搜索成员的特性!!!
下面我们使用代码来测试一下上述的机制,首先是:
搜索成员
|
|
以上说明在setValue
没有找到对应属性时,都会执行到accessInstanceVariablesDirectly
。
|
|
以上说明具有对应的set<Key>
方法时,即使不存在这个属性也可以通过编译。
成员不存在
|
|
以上说明当setValueForKey
和valueForKey
中的Key
无法找到时,会调用响应的方法。
|
|
现在可以顺利的输出了。
取值 valueForKey
当调用ValueforKey:@”name“
的代码时,KVC对key的搜索方式不同于setValue:属性值 forKey:@”name“
,其搜索方式如下:
首先按
get<Key> <key> is<Key>
的顺序方法查找getter方法,找到的话会直接调用。如果是BOOL或者int等值类型, 会做NSNumber转换;如果上面的getter没有找到,KVC则会查找:
countOf<Key> & objectIn<Key>AtIndex & <Key>AtIndex
这三种格式的方法。如果
countOf<Key>
和另外两个方法中的一个被找到,那么就会返回一个可以响应NSArray
所有方法的代理集合,它是NSKeyValueArray
,是NSArray
的子,这个代理集合将拥有以上方法的组合,还有一个可选的get<Key>: range:
方法;所以你想重新定义KVC的一些功能,你可以添加这些方法,需要注意的是你的方法名要符合KVC的标准命名方法,包括方法签名。
如果上面的方法没有找到,那么会查找:
countOf<Key> & enumeratorOf<Key> & memberOf<Key>
以上三种格式的方法,如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合,这个代理集合将拥有以上三种方法。
如果还没有找到,再检查类方法:
+ (BOOL)accessInstanceVariablesDirectly
如果返回YES(默认行为),那么和先前的设值一样,会按
_Key,_isKey,Key,isKey
的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱;如果重写了类方法
+ (BOOL)accessInstanceVariablesDirectly
返回NO的话,那么会直接调用:valueForUndefinedKey:
还没有找到的话,调用
valueForUndefinedKey:
。
下面我们使用代码来验证以上的过程:
|
|
以下的输出结果证明,在valueForKey
方法执行时,如果对应的Key
不存在,则会首先按get<Key> <key> is<Key>
的顺序方法查找getter方法:
|
|
以下结果说明,当Key
不存在,get<Key> <key> is<Key>
方法也不存在时。如果countOf<Key>
和:
objectIn<Key>AtIndex & <Key>AtIndex
两个方法中的一个被找到,那么就会返回该方法:
|
|
KVC中使用KeyPath
在开发过程中,一个类的成员变量有可能是其他的自定义类,你可以先用KVC获取出来再该属性,然后再次用KVC来获取这个自定义类的属性,但这样是比较繁琐的,对此,KVC提供了一个解决方案,那就是键路径KeyPath。
|
|
|
|
上面的代码简单在展示了KeyPath是怎么用的。如果使用了key而非KeyPath的话,KVC会直接查找address.country
这个属性,很明显,这个属性并不存在,所以会再调用UndefinedKey
相关方法。
而KVC对于KeyPath是搜索机制第一步就是分离key,用小数点.
来分割key,然后再像普通key一样按照先前介绍的顺序搜索下去。
KVC异常处理
KVC中最常见的异常就是不小心使用了错误的Key,或者在设值中不小心传递了nil的值,KVC中有专门的方法来处理这些异常。
通常在用KVC操作Model时,抛出异常的那两个方法是需要重写的。虽然一般很小出现传递了错误的Key值这种情况,但是如果不小心出现了,直接抛出异常让APP崩溃显然是不合理的。
一般在这里直接让这个Key打印出来即可,或者有些特殊情况需要特殊处理。
通常情况下,KVC不允许你要在调用setValue:属性值 forKey:@”name“
(或者keyPath)时对非对象传递一个nil的值。很简单,因为值类型是不能为nil的。如果你不小心传了,KVC会调用setNilValueForKey:
方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。
而在Swift中,无论是可选类型还是非可选类型,KVC都允许写入空值,但在使用时如果对非可选类型赋空值,程序将崩溃,如下:
|
|
如上所见,setNilValueForKey:
在Swift中并不会执行。
KVC处理非对象和自定义对象
不是每一个方法都返回对象,但是valueForKey:
总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。这两个类会处理从数字,布尔值到指针和结构体任何类型。然后开发者需要手动转换成原来的类型。
尽管valueForKey:
会自动将值类型封装成对象,但是setValue:forKey:
却不行。你必须手动将值类型转换成NSNumber
或者NSValue
类型,才能传递过去。
对于自定义对象,KVC也会正确的设值和取值。因为传递进去和取出来的都是id类型,所以需要开发者自己保证类型的正确性,运行时Objective-C在发送消息的会检查类型,如果错误会直接抛出异常。
|
|
KVC和字典
从NSDictionary取值的时候有两个方法:
objectForKey & valueForKey:
两种取值方法的区别
这两个方法具体有什么不同呢?先从NSDictionary文档中来看这两个方法的定义:
objectForKey: returns the value associated with aKey, or nil if no value is associated with aKey.
返回给定的key的value,若没有这个Key返回nil。
valueForKey: returns the value associated with a given key.
返回与给定的key的value。
直观上看这两个方法好像没有什么区别,但文档里valueForKey:
有额外一点:
If key does not start with “@”, invokes objectForKey:.
一般来说key可以是任意字符串组合,如果key不是以@符号开头,这时候valueForKey: 等同于objectForKey:,
If key does start with “@”, strips the “@” and invokes [super valueForKey:] with the rest of the key.
如果是以@开头,去掉key里的@然后用剩下部分作为key执行[super valueForKey :]。
|
|
这时候a和b是一样的结果,因为key不是以@符号开头,这时候valueForKey:
等同于objectForKey:
,但如果是这样一个dict:
|
|
b的取值为nil,a取值会直接crash掉,报错信息:
|
|
这是因为valueForKey:
以@开头,去掉key里的@然后用剩下部分作为key执行[super valueForKey :]
,而super没有key
这个属性,所以进入了valueForUndefinedKey
方法中,抛出错误。
总结
objectForKey:
是NSDictionary的方法,valueForKey:
是KVC的方法, 两者都是键值对应,区别是valueForKey:
只允许使用NSString类型,objectForKey:
可以是任意类型。
当对NSDictionary对象使用KVC时,valueForKey:
的表现行为和objectForKey:
一样。所以使用valueForKeyPath:
用来访问多层嵌套的字典是比较方便的。KVC里面还有两个关于NSDictionary的方法:
|
|
下面直接用代码对这两个方法来进行演示:
|
|
Demo下载请点击这里
参考链接
iOS开发技巧系列—详解KVC(我告诉你KVC的一切) - 黑暗中的孤影
KVC进阶(一)- 01_Jack
Objective-C KVC机制 - omegayy